import { v4 as uuidv4 } from 'uuid';
import SparkMD5 from 'spark-md5'; // Import spark-md5
// Use openDB directly from 'idb'
// Removed IDBPObjectStore from import
// Explicitly import IDBPObjectStore along with other types
import { openDB, deleteDB, type IDBPDatabase, type IDBPTransaction, type StoreNames, type DBSchema, type IDBPIndex, type IDBPCursorWithValue, type IDBPObjectStore } from 'idb';

// Define the schema type
// Follow idb v7+ schema structure: store: { key, value }
interface JDFSightDBSchema extends DBSchema {
    jdfFiles: {
        key: number;
        value: StoredJDFFile;
    };
    jdfFolders: {
        key: number;
        value: StoredJDFFolder;
    };
}

// Type alias for store names
export type JDFSightStoreName = StoreNames<JDFSightDBSchema>;

// --- Constants ---
export const ROOT_FOLDER_ID = null; // Represent root with null
const DB_NAME = 'JDFSightDB';
export const FILES_STORE_NAME = 'jdfFiles' as const; // Use const assertion for type safety
export const FOLDERS_STORE_NAME = 'jdfFolders' as const; // Use const assertion
const DB_VERSION = 2; // Increment DB version

// Define the structure for stored file data
export interface StoredJDFFile {
    id: number; // Auto-incrementing primary key (is set by DB)
    uuid: string; // Globally unique identifier
    name: string; // File name
    parentId: string | null; // UUID of the parent folder (or null for root)
    content: string; // File content as string
    createdAt: Date; // Creation timestamp
    lastModified: Date; // Last modification timestamp
    size: number; // 新增：文件大小 (bytes)
    sha1: string; // 新增：SHA-1 哈希值
    sha256: string; // 新增：SHA-256 哈希值
    md5: string; // 新增：MD5 哈希值
}

// Type for adding a new file (id is omitted)
type NewJDFFile = Omit<StoredJDFFile, 'id'>;

// Define the structure for file metadata (excluding content)
export interface StoredJDFFileMeta {
    id: number;
    uuid: string;
    name: string;
    parentId: string | null;
    createdAt: Date;
    lastModified: Date;
    size: number; // 新增：文件大小 (bytes)
    sha1: string; // 新增：SHA-1 哈希值
    sha256: string; // 新增：SHA-256 哈希值
    md5: string; // 新增：MD5 哈希值
}

// --- New Folder Interfaces ---
export interface StoredJDFFolder {
    id: number; // Auto-incrementing primary key (is set by DB)
    uuid: string;
    name: string;
    parentId: string | null; // UUID of the parent folder (or null for root)
    createdAt: Date;
    lastModified: Date; // For potential future use (e.g., renaming)
    size: number; // 新增：文件夹总大小 (bytes), 需要递归计算和维护
}

// Type for adding a new folder (id is omitted)
type NewJDFFolder = Omit<StoredJDFFolder, 'id'>;

export interface StoredJDFFolderMeta {
    id: number;
    uuid: string;
    name: string;
    parentId: string | null;
    createdAt: Date;
    lastModified: Date;
    size: number; // 新增：文件夹总大小 (bytes)
}

// Combined type for directory listing
export type FilesystemItemMeta = (StoredJDFFileMeta & { type: 'file' }) | (StoredJDFFolderMeta & { type: 'folder' });

// Cache the database connection promise
let dbPromise: Promise<IDBPDatabase<JDFSightDBSchema>> | null = null;

/**
 * Gets the database connection.
 */
export function getDB(): Promise<IDBPDatabase<JDFSightDBSchema>> {
    if (!dbPromise) {
        if (!('indexedDB' in window)) {
            console.error("This browser doesn't support IndexedDB.");
            return Promise.reject(new Error("IndexedDB not supported"));
        }

        console.log(`Attempting to open database ${DB_NAME} version ${DB_VERSION}...`);
        dbPromise = openDB<JDFSightDBSchema>(DB_NAME, DB_VERSION, {
            upgrade(db, oldVersion, newVersion, transaction) {
                console.log(`Upgrading database from version ${oldVersion} to ${newVersion || DB_VERSION}...`);

                // --- Version 1 Upgrade (Initial Setup) ---
                if (oldVersion < 1) {
                    if (!db.objectStoreNames.contains(FILES_STORE_NAME)) {
                        const fileStore = db.createObjectStore(FILES_STORE_NAME, {
                            keyPath: 'id',
                            autoIncrement: true,
                        });
                        fileStore.createIndex('uuid', 'uuid', { unique: true });
                        fileStore.createIndex('name', 'name', { unique: false });
                        fileStore.createIndex('lastModified', 'lastModified', { unique: false });
                        console.log(`Object store "${FILES_STORE_NAME}" created with indexes.`);
                    } else {
                        console.log(`Object store "${FILES_STORE_NAME}" already exists.`);
                    }
                }

                // --- Version 2 Upgrade (Adding Folders and parentId index) ---
                if (oldVersion < 2) {
                    // 1. Create Folders Store
                    if (!db.objectStoreNames.contains(FOLDERS_STORE_NAME)) {
                        const folderStore = db.createObjectStore(FOLDERS_STORE_NAME, {
                            keyPath: 'id',
                            autoIncrement: true,
                        });
                        folderStore.createIndex('uuid', 'uuid', { unique: true });
                        folderStore.createIndex('parentId', 'parentId', { unique: false });
                        folderStore.createIndex('name', 'name', { unique: false });
                        folderStore.createIndex('lastModified', 'lastModified', { unique: false }); // Add lastModified index
                        console.log(`Object store "${FOLDERS_STORE_NAME}" created with indexes.`);
                    } else {
                        console.log(`Object store "${FOLDERS_STORE_NAME}" already exists.`);
                    }

                    // 2. Add parentId index to Files Store using the upgrade transaction
                    const fileStore = transaction.objectStore(FILES_STORE_NAME);
                    if (!fileStore.indexNames.contains('parentId')) {
                        fileStore.createIndex('parentId', 'parentId', { unique: false });
                        console.log(`Index "parentId" created on store "${FILES_STORE_NAME}".`);
                    } else {
                        console.log(`Index "parentId" already exists on store "${FILES_STORE_NAME}".`);
                    }
                }
                // Add further upgrade steps here using `if (oldVersion < X)`
                console.log("Database upgrade complete.");
            },
            blocked() {
                console.error('Database open blocked, please close other tabs using the database.');
                // Potentially show a message to the user
                alert('数据库连接被阻塞。请关闭使用此应用的其他标签页，然后刷新。');
            },
            blocking() {
                console.warn('Database is blocking other connections. Closing...');
                // Attempt to close the connection gracefully if this client is blocking
                dbPromise?.then(db => db.close());
                dbPromise = null; // Reset promise so it reopens on next call
            },
            terminated() {
                console.error('Database connection terminated unexpectedly.');
                dbPromise = null; // Reset promise
            },
        }).then(db => {
            console.log(`Database ${DB_NAME} version ${DB_VERSION} opened successfully.`);
            return db;
        }).catch(err => {
            console.error(`Failed to open database ${DB_NAME}:`, err);
            dbPromise = null; // Reset promise on error
            throw err; // Re-throw the error
        });
    }
    return dbPromise;
}

// --- Public API ---

/**
 * Adds a new file to the IndexedDB.
 * @param name - The name of the file.
 * @param content - The content of the file as a string.
 * @param parentId - The UUID of the parent folder (or null for root).
 * @returns A promise that resolves with the metadata of the added file.
 */
export async function addFile(name: string, content: string, parentId: string | null = ROOT_FOLDER_ID): Promise<StoredJDFFileMeta> {
    // --- Step 1: Calculate size and hashes BEFORE starting the transaction ---
    const fileSize = new Blob([content]).size;
    const hashes = await calculateFileHashes(content); // Calculate hashes first
    const now = new Date();
    const newUuid = uuidv4();

    const fileData: NewJDFFile = {
        uuid: newUuid,
        name,
        parentId,
        content,
        createdAt: now,
        lastModified: now,
        size: fileSize,
        sha1: hashes.sha1,
        sha256: hashes.sha256,
        md5: hashes.md5,
    };

    try {
        // --- Step 2: Start transaction and perform DB operations ---
        const db = await getDB();
        const tx = db.transaction([FILES_STORE_NAME, FOLDERS_STORE_NAME], 'readwrite');
        const store = tx.objectStore(FILES_STORE_NAME);

        // Add the file data
        const addedId = await store.add(fileData as any);

        // Update ancestor folder sizes within the same transaction
        await updateAncestorFolderSizes(newUuid, fileSize, tx);

        // Wait for transaction completion
        await tx.done;

        console.log(`File "${name}" added with ID: ${addedId}, UUID: ${newUuid}`);

        // Return metadata (construct outside transaction is fine)
        return {
            id: addedId,
            uuid: newUuid,
            name,
            parentId,
            createdAt: now,
            lastModified: now,
            size: fileSize,
            sha1: hashes.sha1,
            sha256: hashes.sha256,
            md5: hashes.md5,
        };
    } catch (error) {
        console.error('Error adding file inside transaction:', error);
        // Ensure the specific error type is thrown if needed elsewhere
        if (error instanceof Error) {
             throw new Error(`Error adding file: ${error.message}`);
        } else {
             throw new Error(`Error adding file: ${String(error)}`);
        }
       // throw new Error(`Error adding file: ${error instanceof Error ? error.message : String(error)}`); // Original shorter version
    }
}

/**
 * Retrieves the full details of a single file by its UUID.
 * @param uuid - The UUID of the file to retrieve.
 * @returns A promise that resolves with the StoredJDFFile object or undefined if not found.
 */
export async function getFile(uuid: string): Promise<StoredJDFFile | undefined> {
    try {
        const db = await getDB();
        const tx = db.transaction(FILES_STORE_NAME, 'readonly');
        const store = tx.objectStore(FILES_STORE_NAME);
        const index = (store as any).index('uuid'); // Use type assertion to bypass index check
        const file = await index.get(uuid); // Use await
        await tx.done;
        return file;
    } catch (error) {
        console.error('Error getting file:', error);
        throw new Error(`Error getting file: ${error instanceof Error ? error.message : String(error)}`);
    }
}

/**
 * Deletes a file from the IndexedDB by its UUID.
 * @param uuid - The UUID of the file to delete.
 * @returns A promise that resolves when the deletion is complete.
 */
export async function deleteFile(uuid: string): Promise<void> {
    try {
        const db = await getDB();
        // Transaction needs both stores for size update
        const tx = db.transaction([FILES_STORE_NAME, FOLDERS_STORE_NAME], 'readwrite');
        const store = tx.objectStore(FILES_STORE_NAME);
        const index = (store as any).index('uuid');

        // Find the file data first to get its size and primary key
        const fileToDelete = await index.get(uuid);

        if (!fileToDelete) {
            console.warn(`File with UUID ${uuid} not found for deletion.`);
            await tx.done; // Complete the (read) transaction
            return;
        }

        const fileSize = fileToDelete.size;
        const primaryKey = fileToDelete.id;

        // Delete the file record
        await store.delete(primaryKey);

        // Update ancestor folder sizes (after successful deletion)
        await updateAncestorFolderSizes(uuid, -fileSize, tx);

        await tx.done; // Wait for transaction completion
        console.log(`File with UUID ${uuid} (ID: ${primaryKey}) deleted successfully.`);

    } catch (error) {
        console.error('Error deleting file:', error);
        throw new Error(`Error deleting file: ${error instanceof Error ? error.message : String(error)}`);
    }
}

/**
 * Updates the content and lastModified timestamp of an existing file.
 * @param uuid - The UUID of the file to update.
 * @param content - The new content for the file.
 * @returns A promise that resolves when the update is complete.
 */
export async function updateFileContent(uuid: string, content: string): Promise<void> {
    try {
        const db = await getDB();
        // Transaction needs both stores for size update
        const tx = db.transaction([FILES_STORE_NAME, FOLDERS_STORE_NAME], 'readwrite');
        const store = tx.objectStore(FILES_STORE_NAME);
        const index = (store as any).index('uuid');

        const oldFileData = await index.get(uuid); // Get old data first

        if (!oldFileData) {
            throw new Error(`File with UUID ${uuid} not found for update.`);
        }

        const oldFileSize = oldFileData.size;

        // Calculate new size and hashes
        const newFileSize = new Blob([content]).size;
        const newHashes = await calculateFileHashes(content);

        // Update content, timestamp, size, and hashes
        const updatedFileData: StoredJDFFile = {
            ...oldFileData,
            content,
            lastModified: new Date(),
            size: newFileSize,
            sha1: newHashes.sha1,
            sha256: newHashes.sha256,
            md5: newHashes.md5,
        };

        // Put the updated data
        await store.put(updatedFileData);

        // Calculate size delta and update ancestors if necessary
        const sizeDelta = newFileSize - oldFileSize;
        if (sizeDelta !== 0) {
            await updateAncestorFolderSizes(uuid, sizeDelta, tx);
        }

        await tx.done; // Wait for transaction completion
        console.log(`File content for UUID ${uuid} updated successfully.`);

    } catch (error) {
        console.error('Error updating file content:', error);
        if (error instanceof Error && error.message.includes('not found')) {
            throw error; // Re-throw specific error
        }
        throw new Error(`Error updating file content: ${error instanceof Error ? error.message : String(error)}`);
    }
}


// --- Internal Recursive Delete Helper ---
/**
 * Recursively deletes all files and subfolders within a given folder ID using an existing transaction.
 * IMPORTANT: Call this function only within a readwrite transaction spanning both stores.
 * @param parentId The UUID of the folder whose contents are to be deleted.
 * @param tx The active readwrite IDBPTransaction covering FILES_STORE_NAME and FOLDERS_STORE_NAME.
 */
async function _recursiveDeleteFolderContents(
    parentId: string,
    tx: IDBPTransaction<JDFSightDBSchema, [typeof FILES_STORE_NAME, typeof FOLDERS_STORE_NAME], "readwrite">
): Promise<void> {
    const filesStore = tx.objectStore(FILES_STORE_NAME);
    const foldersStore = tx.objectStore(FOLDERS_STORE_NAME);
    const filesIndex = (filesStore as any).index('parentId'); // Use type assertion
    const foldersIndex = (foldersStore as any).index('parentId'); // Use type assertion

    // Correctly await and type keys from getAllKeys
    const filesToDeletePrimaryKeys: number[] = await filesIndex.getAllKeys(IDBKeyRange.only(parentId));
    const foldersToDelete: StoredJDFFolder[] = await foldersIndex.getAll(IDBKeyRange.only(parentId));

    // Delete files in parallel
    if (filesToDeletePrimaryKeys.length > 0) {
        console.log(`[RecDel] Found ${filesToDeletePrimaryKeys.length} files to delete in folder ${parentId}`);
        // Ensure keys are numbers before calling delete
        const fileDeletePromises = filesToDeletePrimaryKeys.map((key: number) => {
            console.log(`[RecDel] Deleting file with primary key: ${key}`);
            return filesStore.delete(key);
        });
        await Promise.all(fileDeletePromises);
        console.log(`[RecDel] Finished deleting files in folder ${parentId}`);
    } else {
        console.log(`[RecDel] No files found to delete in folder ${parentId}`);
    }

    // Delete subfolders recursively and in parallel
    if (foldersToDelete.length > 0) {
        console.log(`[RecDel] Found ${foldersToDelete.length} subfolders to delete in folder ${parentId}`);
        const folderDeletePromises = foldersToDelete.map(async (folder: StoredJDFFolder) => {
            // Recursive call
            console.log(`[RecDel] Starting recursive delete for subfolder ${folder.name} (UUID: ${folder.uuid})`);
            await _recursiveDeleteFolderContents(folder.uuid, tx);
            // Delete the subfolder itself using its primary key
            console.log(`[RecDel] Deleting subfolder entry ${folder.name} (ID: ${folder.id})`);
            await foldersStore.delete(folder.id);
            console.log(`[RecDel] Finished deleting subfolder ${folder.name} (UUID: ${folder.uuid})`);
        });
        await Promise.all(folderDeletePromises);
        console.log(`[RecDel] Finished deleting subfolders in folder ${parentId}`);
    } else {
        console.log(`[RecDel] No subfolders found to delete in folder ${parentId}`);
    }
}

/**
 * Deletes a folder entry AND all its contents (files and subfolders) recursively from IndexedDB.
 * @param uuid - The UUID of the folder to delete.
 * @returns A promise that resolves when the recursive deletion is complete.
 */
export async function deleteFolder(uuid: string): Promise<void> {
    let tx: IDBPTransaction<JDFSightDBSchema, [typeof FILES_STORE_NAME, typeof FOLDERS_STORE_NAME], "readwrite"> | null = null;
    try {
        const db = await getDB();
        tx = db.transaction([FILES_STORE_NAME, FOLDERS_STORE_NAME], 'readwrite');
        const foldersStore = tx.objectStore(FOLDERS_STORE_NAME);
        const index = (foldersStore as any).index('uuid');

        // Find the primary key (id) and the folder data (for size) of the target folder
        const folderToDelete = await index.get(uuid);

        if (!folderToDelete) {
            console.warn(`[deleteFolder] Folder with UUID ${uuid} not found for deletion.`);
            await tx.done;
            return;
        }
        const primaryKey = folderToDelete.id;
        const folderSize = folderToDelete.size;

        console.log(`[deleteFolder] Starting recursive delete for folder UUID: ${uuid} (ID: ${primaryKey}), Size: ${folderSize}`);
        // Pass the active transaction to the recursive helper
        await _recursiveDeleteFolderContents(uuid, tx);

        // Finally, delete the target folder itself using its primary key
        console.log(`[deleteFolder] Deleting target folder entry ${uuid} (ID: ${primaryKey})`);
        await foldersStore.delete(primaryKey);
        console.log(`[deleteFolder] Successfully deleted target folder ${uuid}`);

        // Update ancestor folder sizes (after successful deletion of the folder entry)
        await updateAncestorFolderSizes(uuid, -folderSize, tx);

        // Wait for the transaction to complete using .done
        await tx.done;
        console.log(`[deleteFolder] Transaction completed for ${uuid}`);

    } catch (error) {
        console.error(`[deleteFolder] Error during recursive delete for ${uuid}:`, error);
        if (tx && !tx.done) {
            try {
                console.log(`[deleteFolder] Aborting transaction for ${uuid}`);
                tx.abort(); // Use abort on the IDBPTransaction
                // tx.done.catch(() => {}); // Removed - Catch outside
                console.log(`[deleteFolder] Transaction aborted for ${uuid}`);
            } catch (abortError) {
                console.error(`[deleteFolder] Error aborting transaction for ${uuid}:`, abortError);
            }
        }
        // Check if the original error was due to abort
        if (!(error instanceof DOMException && error.name === 'AbortError')) {
            // Re-throw the original error only if it wasn't an abort
            throw new Error(`Error deleting folder: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
}


/**
 * Renames a file or folder.
 * @param uuid - The UUID of the item to rename.
 * @param newName - The new name.
 * @param isFolder - True if the item is a folder, false if it's a file.
 * @returns A promise that resolves when the rename is complete.
 */
export async function renameItem(uuid: string, newName: string, isFolder: boolean): Promise<void> {
    const storeName = isFolder ? FOLDERS_STORE_NAME : FILES_STORE_NAME;
    try {
        const db = await getDB();
        const tx = db.transaction(storeName, 'readwrite');
        const store = tx.objectStore(storeName);
        const index = (store as any).index('uuid'); // Use type assertion to bypass index check

        // Type assertion based on isFolder
        const itemData = await index.get(uuid) as StoredJDFFile | StoredJDFFolder | undefined;

        if (!itemData) {
            const itemType = isFolder ? 'Folder' : 'File';
            throw new Error(`${itemType} with UUID ${uuid} not found for rename.`);
        }

        // Update name and timestamp
        const updatedItemData = {
            ...itemData,
            name: newName,
            lastModified: new Date(),
        };

        await store.put(updatedItemData); // Use put to overwrite
        await tx.done; // Wait for transaction completion
        console.log(`${isFolder ? 'Folder' : 'File'} name for UUID ${uuid} updated to "${newName}".`);

    } catch (error) {
        const itemType = isFolder ? 'folder' : 'file';
        console.error(`Error renaming ${itemType}:`, error);
        if (error instanceof Error && error.message.includes('not found')) {
            throw error; // Re-throw specific error
        }
        throw new Error(`Error renaming ${itemType}: ${error instanceof Error ? error.message : String(error)}`);
    }
}

// --- FOLDER CRUD ---

/**
 * Adds a new folder to the IndexedDB.
 * @param name - The name of the folder.
 * @param parentId - The UUID of the parent folder (or null for root).
 * @returns A promise that resolves with the metadata of the added folder.
 */
export async function addFolder(name: string, parentId: string | null = ROOT_FOLDER_ID): Promise<StoredJDFFolderMeta> {
    try {
        const db = await getDB();
        const tx = db.transaction(FOLDERS_STORE_NAME, 'readwrite');
        const store = tx.objectStore(FOLDERS_STORE_NAME);
        const now = new Date();
        const newUuid = uuidv4();

        const folderData: NewJDFFolder = {
            uuid: newUuid,
            name,
            parentId,
            createdAt: now,
            lastModified: now,
            size: 0,
        };

        // Cast to any to bypass strict type checking for auto-incrementing key
        const addedId = await store.add(folderData as any); 
        await tx.done; // Wait for transaction completion

        console.log(`Folder "${name}" added with ID: ${addedId}, UUID: ${newUuid}`);
        return {
            id: addedId,
            uuid: newUuid,
            name,
            parentId,
            createdAt: now,
            lastModified: now,
            size: 0,
        };
    } catch (error) {
        console.error('Error adding folder:', error);
        throw new Error(`Error adding folder: ${error instanceof Error ? error.message : String(error)}`);
    }
}

/**
 * Retrieves the full details of a single folder by its UUID.
 * @param uuid - The UUID of the folder to retrieve.
 * @returns A promise that resolves with the StoredJDFFolder object or undefined if not found.
 */
export async function getFolder(uuid: string): Promise<StoredJDFFolder | undefined> {
    try {
        const db = await getDB();
        const tx = db.transaction(FOLDERS_STORE_NAME, 'readonly');
        const store = tx.objectStore(FOLDERS_STORE_NAME);
        const index = (store as any).index('uuid'); // Use type assertion
        const folder = await index.get(uuid); // Use await
        await tx.done;
        return folder;
    } catch (error) {
        console.error('Error getting folder:', error);
        throw new Error(`Error getting folder: ${error instanceof Error ? error.message : String(error)}`);
    }
}

/**
 * Retrieves metadata for all files and folders within a specific folder.
 * Uses helper functions that correctly handle null parentId.
 * @param parentId - The UUID of the parent folder (null for root).
 * @returns A promise that resolves with an array of FilesystemItemMeta objects.
 */
export async function getItemsByParent(parentId: string | null = ROOT_FOLDER_ID): Promise<FilesystemItemMeta[]> {
    try {
        // Fetch files and folders in parallel using helper functions
        const filePromise = getFilesMetaByParent(parentId).catch((err: Error) => {
            console.error(`Error fetching files metadata for parent ${parentId}:`, err);
            return []; // Return empty array on error
        });
        const folderPromise = getFoldersMetaByParent(parentId).catch((err: Error) => {
            console.error(`Error fetching folders metadata for parent ${parentId}:`, err);
            return []; // Return empty array on error
        });

        const [files, folders] = await Promise.all([filePromise, folderPromise]);

        // Combine and add type property
        const combinedItems: FilesystemItemMeta[] = [
            ...folders.map((f: StoredJDFFolderMeta) => ({ ...f, type: 'folder' as const })),
            ...files.map((f: StoredJDFFileMeta) => ({ ...f, type: 'file' as const })),
        ];

        // Sort folders first, then files, then by name alphabetically
        combinedItems.sort((a, b) => {
            if (a.type === 'folder' && b.type === 'file') return -1;
            if (a.type === 'file' && b.type === 'folder') return 1;
            return a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }); // Natural sort
        });

        return combinedItems;
    } catch (error) {
        console.error('Error getting items by parent:', error);
        throw error; // Re-throw the error
    }
}

/**
 * Moves an item (file or folder) to a new parent folder by updating its parentId.
 * @param uuid - The UUID of the item to move.
 * @param newParentId - The UUID of the new parent folder (null for root).
 * @param isFolder - True if the item being moved is a folder, false if it's a file.
 * @returns A promise that resolves when the update is complete.
 */
export async function moveItem(uuid: string, newParentId: string | null, isFolder: boolean): Promise<void> {
    const storeName = isFolder ? FOLDERS_STORE_NAME : FILES_STORE_NAME;
    try {
        const db = await getDB();
        // Transaction needs both stores for size update (folders) and potentially moving files
        const tx = db.transaction([FILES_STORE_NAME, FOLDERS_STORE_NAME], 'readwrite');
        const store = tx.objectStore(storeName);
        const index = (store as any).index('uuid');

        // Get the item to move first to know its size and old parent
        const itemToMove = await index.get(uuid) as StoredJDFFile | StoredJDFFolder | undefined;

        if (!itemToMove) {
            throw new Error(`[moveItem] Item with UUID ${uuid} not found in ${storeName}.`);
        }

        const itemSize = itemToMove.size;
        const oldParentId = itemToMove.parentId;

        // Basic check: Prevent moving to the same folder
        if (oldParentId === newParentId) {
            console.warn(`[moveItem] Item ${uuid} is already in parent ${newParentId}. No move needed.`);
            await tx.done;
            return;
        }

        // --- Update sizes for the OLD parent path --- (Before changing parentId)
        if (oldParentId !== null) {
             console.log(`[moveItem UpdSize] Decreasing size for old parent path of ${uuid} (Delta: ${-itemSize})`);
             await updateAncestorFolderSizes(uuid, -itemSize, tx);
        }

        // Update parentId and lastModified
        const updatedItem = {
            ...itemToMove,
            parentId: newParentId,
            lastModified: new Date(),
        };

        // --- Perform the move (update the item's parentId) ---
        await store.put(updatedItem);

        // --- Update sizes for the NEW parent path --- (After changing parentId)
        if (newParentId !== null) {
             console.log(`[moveItem UpdSize] Increasing size for new parent path of ${uuid} (Delta: ${itemSize})`);
             await updateAncestorFolderSizes(uuid, itemSize, tx);
        }

        await tx.done; // Wait for transaction completion
        console.log(`[moveItem] Item ${uuid} moved to parent ${newParentId} in ${storeName}.`);

    } catch (error) {
        console.error('[moveItem] Error:', error);
        if (error instanceof Error && error.message.includes('not found')) {
            throw error; // Re-throw specific error
        }
        throw new Error(`Error moving item: ${error instanceof Error ? error.message : String(error)}`);
    }
}

// --- Helper functions using async iterators for cursors ---

/**
 * Iterates over a store or index cursor using async iterator pattern.
 * @param storeOrIndex - The IDBPObjectStore or IDBPIndex to iterate.
 * @param range - Optional IDBKeyRange for the cursor.
 * @param filterFn - Optional function to filter results based on value.
 * @returns An async generator yielding cursor values.
 */
async function* iterateCursor<T, S extends JDFSightStoreName>(
    storeOrIndex: IDBPObjectStore<JDFSightDBSchema, [S], S, 'readonly' | 'readwrite'> | IDBPIndex<JDFSightDBSchema, [S], S, any, 'readonly' | 'readwrite'>,
    range?: IDBKeyRange | null,
    filterFn?: (value: T) => boolean
): AsyncGenerator<T> {
    let cursor = await storeOrIndex.openCursor(range);
    while (cursor) {
        const value = cursor.value as T;
        if (!filterFn || filterFn(value)) {
            yield value;
        }
        cursor = await cursor.continue();
    }
}

/**
 * Retrieves metadata for all files within a specific folder using async iteration.
 * @param parentId - The UUID of the parent folder (null for root).
 * @returns A promise that resolves with an array of StoredJDFFileMeta objects.
 */
async function getFilesMetaByParent(parentId: string | null = ROOT_FOLDER_ID): Promise<StoredJDFFileMeta[]> {
    const db = await getDB();
    const tx = db.transaction(FILES_STORE_NAME, 'readonly');
    const store = tx.objectStore(FILES_STORE_NAME);
    const filesMeta: StoredJDFFileMeta[] = [];

    try {
        let iterator: AsyncGenerator<StoredJDFFile>;
        if (parentId !== null) {
            console.log(`[getFilesMetaByParent] Querying using parentId INDEX for parentId: ${parentId}`);
            const index = (store as any).index('parentId');
            iterator = iterateCursor<StoredJDFFile, typeof FILES_STORE_NAME>(index, IDBKeyRange.only(parentId));
        } else {
            console.log('[getFilesMetaByParent] Querying using STORE cursor for parentId: null');
            iterator = iterateCursor<StoredJDFFile, typeof FILES_STORE_NAME>(store, null, (file) => file.parentId === null);
        }

        for await (const fileData of iterator) {
            filesMeta.push({
                id: fileData.id,
                uuid: fileData.uuid,
                name: fileData.name,
                parentId: fileData.parentId,
                createdAt: fileData.createdAt,
                lastModified: fileData.lastModified,
                size: fileData.size,
                sha1: fileData.sha1,
                sha256: fileData.sha256,
                md5: fileData.md5,
            });
        }

        await tx.done;
        console.log(`[getFilesMetaByParent] Finished. Found ${filesMeta.length} files for parentId: ${parentId}`);
        return filesMeta;
    } catch (error) {
        console.error('Error getting files metadata by parent:', error);
        throw new Error(`Error getting files metadata by parent: ${error instanceof Error ? error.message : String(error)}`);
    }
}


/**
 * Retrieves metadata for all folders within a specific folder using async iteration.
 * @param parentId - The UUID of the parent folder (null for root).
 * @returns A promise that resolves with an array of StoredJDFFolderMeta objects.
 */
async function getFoldersMetaByParent(parentId: string | null = ROOT_FOLDER_ID): Promise<StoredJDFFolderMeta[]> {
    const db = await getDB();
    const tx = db.transaction(FOLDERS_STORE_NAME, 'readonly');
    const store = tx.objectStore(FOLDERS_STORE_NAME);
    const foldersMeta: StoredJDFFolderMeta[] = [];

    try {
        let iterator: AsyncGenerator<StoredJDFFolder>;
        if (parentId !== null) {
            console.log(`[getFoldersMetaByParent] Querying using parentId INDEX for parentId: ${parentId}`);
            const index = (store as any).index('parentId');
            iterator = iterateCursor<StoredJDFFolder, typeof FOLDERS_STORE_NAME>(index, IDBKeyRange.only(parentId));
        } else {
            console.log('[getFoldersMetaByParent] Querying using STORE cursor for parentId: null');
            iterator = iterateCursor<StoredJDFFolder, typeof FOLDERS_STORE_NAME>(store, null, (folder) => folder.parentId === null);
        }

        for await (const folderData of iterator) {
            foldersMeta.push({
                id: folderData.id,
                uuid: folderData.uuid,
                name: folderData.name,
                parentId: folderData.parentId,
                createdAt: folderData.createdAt,
                lastModified: folderData.lastModified,
                size: folderData.size,
            });
        }

        await tx.done;
        console.log(`[getFoldersMetaByParent] Finished. Found ${foldersMeta.length} folders for parentId: ${parentId}`);
        return foldersMeta;
    } catch (error) {
        console.error('Error getting folders metadata by parent:', error);
        throw new Error(`Error getting folders metadata by parent: ${error instanceof Error ? error.message : String(error)}`);
    }
}

/**
 * Retrieves only the names of all files and folders within a specific folder.
 * Uses helper functions that correctly handle null parentId.
 * @param parentId - The UUID of the parent folder (null for root).
 * @returns A promise that resolves with an array of item names (strings).
 */
export async function getItemNamesByParent(parentId: string | null = ROOT_FOLDER_ID): Promise<string[]> {
    try {
        // Fetch metadata using helpers
        const filePromise = getFilesMetaByParent(parentId).catch(() => []);
        const folderPromise = getFoldersMetaByParent(parentId).catch(() => []);

        const [files, folders] = await Promise.all([filePromise, folderPromise]);

        // Extract names
        const fileNames = files.map(f => f.name);
        const folderNames = folders.map(f => f.name);

        return [...fileNames, ...folderNames];
    } catch (error) {
        console.error('[getItemNamesByParent] Error:', error);
        throw error;
    }
}

/**
 * Retrieves metadata for a single item (file or folder) by its name and parent folder ID.
 * @param name - The name of the item to find.
 * @param parentId - The UUID of the parent folder (null for root).
 * @returns A promise that resolves with the FilesystemItemMeta object or null if not found.
 */
export async function getItemMetaByNameAndParent(name: string, parentId: string | null): Promise<FilesystemItemMeta | null> {
    try {
        const db = await getDB();
        const tx = db.transaction([FILES_STORE_NAME, FOLDERS_STORE_NAME], 'readonly');
        const fileStore = tx.objectStore(FILES_STORE_NAME);
        const folderStore = tx.objectStore(FOLDERS_STORE_NAME);
        const fileIndex = (fileStore as any).index('parentId');
        const folderIndex = (folderStore as any).index('parentId');
        let foundItem: StoredJDFFile | StoredJDFFolder | null = null;
        let itemType: 'file' | 'folder' | null = null;

        // --- Search Files --- 
        const fileRange = parentId === null ? null : IDBKeyRange.only(parentId);
        let fileCursor = await fileIndex.openCursor(fileRange);
        while (fileCursor) {
            if (fileCursor.value.name === name) {
                foundItem = fileCursor.value;
                itemType = 'file';
                break; // Found it, stop searching files
            }
            fileCursor = await fileCursor.continue();
        }

        // --- Search Folders (only if not found in files) ---
        if (!foundItem) {
            const folderRange = parentId === null ? null : IDBKeyRange.only(parentId);
            let folderCursor = await folderIndex.openCursor(folderRange);
            while (folderCursor) {
                if (folderCursor.value.name === name) {
                    foundItem = folderCursor.value;
                    itemType = 'folder';
                    break; // Found it, stop searching folders
                }
                folderCursor = await folderCursor.continue();
            }
        }

        await tx.done;

        if (foundItem && itemType) {
            // Construct the metadata object
            // Add explicit type assertion to ensure compatibility
            const meta: FilesystemItemMeta = {
                id: foundItem.id,
                uuid: foundItem.uuid,
                name: foundItem.name,
                parentId: foundItem.parentId,
                createdAt: foundItem.createdAt,
                lastModified: foundItem.lastModified,
                type: itemType,
                size: foundItem.size,
                sha1: itemType === 'file' ? (foundItem as StoredJDFFile).sha1 : '',
                sha256: itemType === 'file' ? (foundItem as StoredJDFFile).sha256 : '',
                md5: itemType === 'file' ? (foundItem as StoredJDFFile).md5 : '',
            };
            return meta;
        } else {
            return null; // Not found
        }

    } catch (error) {
        console.error(`[getItemMetaByNameAndParent] Error finding item '${name}' in parent '${parentId}':`, error);
        throw new Error(`查找项目时出错: ${error instanceof Error ? error.message : String(error)}`);
    }
}

// --- Helper: Convert ArrayBuffer to Hex String ---
function bufferToHex(buffer: ArrayBuffer): string {
    return [...new Uint8Array(buffer)]
        .map(b => b.toString(16).padStart(2, '0'))
        .join('');
}

// --- Helper: Calculate File Hashes ---
/**
 * Calculates MD5, SHA-1 and SHA-256 hashes for given string content.
 * Uses SparkMD5 for MD5 and Web Crypto API for SHA1/SHA256.
 * @param content The file content as a string.
 * @returns A promise resolving to an object with md5, sha1 and sha256 hex strings.
 */
async function calculateFileHashes(content: string): Promise<{ md5: string; sha1: string; sha256: string }> {
    // MD5 Calculation (Synchronous using SparkMD5)
    const md5Hash = SparkMD5.hash(content);

    // SHA1/SHA256 Calculation (Asynchronous using Web Crypto)
    let sha1Hex = '';
    let sha256Hex = '';

    if (typeof crypto !== 'undefined' && crypto.subtle) {
        try {
            const encoder = new TextEncoder();
            const data = encoder.encode(content);

            const [sha1Buffer, sha256Buffer] = await Promise.all([
                crypto.subtle.digest('SHA-1', data),
                crypto.subtle.digest('SHA-256', data)
            ]);

            sha1Hex = bufferToHex(sha1Buffer);
            sha256Hex = bufferToHex(sha256Buffer);
        } catch (error) {
            console.error('Error calculating SHA hashes:', error);
            // Keep SHA hashes empty on error
        }
    } else {
        console.warn('Web Crypto API not available. Skipping SHA hash calculation.');
    }

    return { md5: md5Hash, sha1: sha1Hex, sha256: sha256Hex };
}

// --- Helper: Update Ancestor Folder Sizes Recursively ---
/**
 * Recursively updates the size of a folder and its ancestors.
 * MUST be called within an active readwrite transaction covering jdfFiles and jdfFolders.
 * @param targetChildUuid - The UUID of the item whose parent's size needs updating.
 * @param sizeDelta - The change in size (positive for increase, negative for decrease).
 * @param tx - The active readwrite transaction.
 */
async function updateAncestorFolderSizes(
    targetChildUuid: string,
    sizeDelta: number,
    tx: IDBPTransaction<JDFSightDBSchema, JDFSightStoreName[], "readwrite">
): Promise<void> {
    if (sizeDelta === 0) {
        // console.log(`[UpdSize] Size delta is 0 for child ${targetChildUuid}, no update needed.`);
        return; // No change, no need to update
    }

    const filesStore = tx.objectStore(FILES_STORE_NAME);
    const foldersStore = tx.objectStore(FOLDERS_STORE_NAME);

    // Find the direct parent ID of the target item
    // We need to check both stores as we only have the child UUID
    let parentId: string | null | undefined = undefined;
    const fileIndex = (filesStore as any).index('uuid');
    const folderIndex = (foldersStore as any).index('uuid');

    const childItem = await fileIndex.get(targetChildUuid) ?? await folderIndex.get(targetChildUuid);

    if (!childItem) {
        console.warn(`[UpdSize] Could not find item with UUID ${targetChildUuid} to determine parent.`);
        return; // Should not happen in normal flow
    }
    parentId = childItem.parentId;

    // console.log(`[UpdSize] Updating ancestors for child ${targetChildUuid}. Parent ID: ${parentId}, Delta: ${sizeDelta}`);

    // Start recursion from the parent
    let currentParentId = parentId;
    while (currentParentId !== null) {
        const parentFolderIndex = (foldersStore as any).index('uuid');
        const parentFolder = await parentFolderIndex.get(currentParentId);

        if (!parentFolder) {
            console.error(`[UpdSize] Error: Could not find parent folder with UUID ${currentParentId} during size update.`);
            // Abort transaction? Or just stop recursion?
            // Let's stop recursion for now.
            tx.abort(); // Abort the transaction as data is inconsistent
            throw new Error(`Parent folder ${currentParentId} not found during size update.`);
        }

        const newSize = (parentFolder.size || 0) + sizeDelta;
        // console.log(`[UpdSize] Updating folder ${parentFolder.name} (${currentParentId}): Old Size=${parentFolder.size}, New Size=${newSize}`);

        const updatedParentFolder: StoredJDFFolder = {
            ...parentFolder,
            size: newSize < 0 ? 0 : newSize, // Ensure size doesn't go below zero
            lastModified: new Date() // Also update lastModified when size changes?
        };

        await foldersStore.put(updatedParentFolder);

        // Move up to the next parent
        currentParentId = parentFolder.parentId;
    }
     console.log(`[UpdSize] Finished updating ancestor sizes starting from child ${targetChildUuid}.`);
}